<?php
// created by Scott Szretter 2/16/10
// some code borrowed from zend examples

// make sure php is installed / path set to it
// make sure php.ini exists, and openssl is enabled (uncomment) and you m ay have
//   to fix the extension path  --  ./ext  instead of  ./
//  hint: I found the zip package for php has everything, but the php installer does not

// I take no responsibility for any damages caused by this code/application - please be sure to 
// BACK UP ALL DATA - Google and ToDoList before running or experimenting with this!

// Please submit any fixes, changes or improvements, thanks!

// you should name your main calendar something like "Appointments", and use that category to when adding appointments in to ToDoList

// NOTES:
//  does not support moving a task from one calendar (category) to another on google side
//  TODO:   OR changing category in todolist (need to do this at least)
//	TODO: support rich text comments to/from google?  currently it sends plain text comment to google
//  TODO: reminder alerts (sample code is here)
//  TODO: add more attributes to sync?
//  TODO: make this a stand-alone app - had troube with winbinder, roadsend, phc-win
//  *** SEE OTHER TO DO ITEMS BELOW


require_once 'Zend/Loader.php';

//TODO:Make this an array of files?  or read all .tdl files in a dir?
$toDoPath="C:\\Users\\cmate\\Documents\\My Dropbox\\todolist\\TODO.tdl";
$googUName="yourgmailaccount@gmail.com";
$googPas="yourgmailpassword";

$aGCalEvents=array();

Zend_Loader::loadClass('Zend_Gdata');
Zend_Loader::loadClass('Zend_Gdata_AuthSub');
Zend_Loader::loadClass('Zend_Gdata_ClientLogin');
Zend_Loader::loadClass('Zend_Gdata_HttpClient');
Zend_Loader::loadClass('Zend_Gdata_Calendar');

function getClientLoginHttpClient($user, $pass)
{
	$service = Zend_Gdata_Calendar::AUTH_SERVICE_NAME;

	$client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $service);
	return $client;
}




//function setReminder($client, $eventId, $minutes=15)
//{
//  $gc = new Zend_Gdata_Calendar($client);
//  $method = "alert";
//  if ($event = getEvent($client, $eventId)) {
//    $times = $event->when;
//    foreach ($times as $when) {
//        $reminder = $gc->newReminder();
//        $reminder->setMinutes($minutes);
//        $reminder->setMethod($method);
//        $when->reminder = array($reminder);
//    }
//    $eventNew = $event->save();
//    return $eventNew;
//  } else {
//    return null;
//  }
//}


//function deleteEventByUrl ($client, $url)
//{
//  $gdataCal = new Zend_Gdata_Calendar($client);
//  $gdataCal->delete($url);
//}


function createNewCalendar ($calendarTitle, &$calFeed, $client,$gdataCal)
{
	//create calendar

	//Set this to true by default, only gets set to false if calendar is found
	$noAppCal = true;


	//Loop through calendars and check name which is ->title->text
	foreach ($calFeed as $calendar) {
		if($calendar -> title -> text == $calendarTitle) {
			$noAppCal = false;
			$theCalURL=$calendar->content->src;
		}
	}

	//If name not found, create the calendar
	if($noAppCal) {

		// I actually had to guess this method based on Google API's "magic" factory
		$appCal = $gdataCal -> newListEntry();
		// I only set the title, other options like color are available.
		$appCal -> title = $gdataCal-> newTitle($calendarTitle);

		//This is the right URL to post to for new calendars...
		//Notice that the user's info is nowhere in there
		$own_cal = "http://www.google.com/calendar/feeds/default/owncalendars/full";

		//And here's the payoff.
		//Use the insertEvent method, set the second optional var to the right URL
		$gdataCal->insertEvent($appCal, $own_cal);

		$calFeed = $gdataCal->getCalendarListFeed(); //update the calendar feed so it has the new calendar info
		echo $gdataCal->content->src;
		return $gdataCal->content->src;
	}

	return $theCalURL; //return existing calendar url

}


function loopTASKS(&$theXML) //loops through the xml file, parents, children looking for tasks with a start date
{   // maybe this could be improved with xpath?


global $gdataCal,$client,$calFeed,$aGCalEvents;
$anyTasksProcessed=0;

//loop each task, if it has children, recursively call and loop more
foreach ($theXML->TASK as $task) {
	// echo $task->attributes()->TITLE ." ". count($task) . "\n";

	$stDate=(string)$task->attributes()->STARTDATESTRING;
	$dueDate=(string)$task->attributes()->DUEDATESTRING;
	$modDate=(string)$task->attributes()->LASTMODSTRING;
	$tCategory=(string)$task->attributes()->CATEGORY;
	$tTile=(string)$task->attributes()->TITLE;
	$tComments=(string) $task->attributes()->COMMENTS;
	$tEstimate=(string) $task->attributes()->TIMEESTIMATE;
	$tTimeUnits=(string) $task->attributes()->TIMEESTUNITS;
	$tTaskID=(string)$task->attributes()->ID;
	 
	//$taskStartDT=strtotime($stDate);
	$dueDateDT=strtotime($dueDate);
	 
	$lModDate=(string)$task->attributes()->LASTMODSTRING;
	$taskModDT=strtotime($lModDate);
	$taskEXTId=(string)$task->attributes()->EXTERNALID;
	 
	//TODO: get reccurence element and attributes

	 
	$createTask=0; //1=ok to create this new task in google
	$changeToDoListFields=0; //are we modifying anything in the todolist data?
	 
	if($dueDate !="") //has to have either a due date so we know where to put it
	{
		if($anyTasksProcessed<1) $anyTasksProcessed=1; //1=have tasks with due dates

		$taskCalURL=createNewCalendar($tCategory,$calFeed,$client,$gdataCal); //create calendar if it does not exist
		 
		//no id so does not exist in gcal and due in future so create it
		//  also check if the event even exists (may have been deleted)!
		if(($taskEXTId=="" || !$aGCalEvents[$taskEXTId]) && $dueDateDT>=time())
		{
			$createTask=1;
		}
		else
		{
			if($taskEXTId!="") //already syned up to GCal check if modified
			{
				// *** MODIFY GCAL EVENT
				if($aGCalEvents[$taskEXTId]["eModDT"]<$taskModDT)
				{

					if ($eventOld = $gdataCal->getCalendarEventEntry($taskEXTId)) {
						echo "modify google ".$aGCalEvents[$taskEXTId]["eModDT"]." ".$taskModDT;
						$eventOld->title = $tTile;
						$eventStart = date('Y-m-d\TH:i:sP', $dueDateDT);


						$date = new DateTime(date('Y-m-d H:i:s', $dueDateDT));
						date_add($date, new DateInterval("PT".round($tEstimate,0).$tTimeUnits));
						$eventEnd = $date->format("Y-m-d\TH:i:sP");

						$eventOld->title = $gdataCal->newTitle($tTile);
						$when = $gdataCal->newWhen();
						$when->startTime = $eventStart;
						$when->endTime = $eventEnd;
						$eventOld->when = array($when);
						//$eventOld->where  = array($gdataCal->newWhere($where)); //todolist does not support location?
						$eventOld->content = $gdataCal->newContent($tComments);
						$eventOld->content->type = 'text'; //html??
							
						$extProp = $gdataCal->newExtendedProperty("TDL_GoodLastMod", time());
						$extProps = array_merge($eventOld->extendedProperty, array($extProp));
						$extProp = $gdataCal->newExtendedProperty("TDL_TaskID", $tTaskID);
						$extProps = array_merge($extProps, array($extProp));
						$eventOld->extendedProperty=$extProps;
							
						//TODO: add reccurence string  $eventOld->recurrence
							//    $recurData =
//        "DTSTART;VALUE=DATE:20070501\r\n" .
//        "DTEND;VALUE=DATE:20070502\r\n" .
//        "RRULE:FREQ=WEEKLY;BYDAY=Tu;UNTIL=20070904\r\n";
//  }
//
//  $newEntry->recurrence = $gc->newRecurrence($recurData);
//

						
					//	try {
							$eventOld->save();
							//get the updated event from google and set the local mod date the same as google so no sync loop
							if ($eventNew = $gdataCal->getCalendarEventEntry($taskEXTId)) {
								// modify local ToDoList modified date/time so we dont sync back to google next time (google modified date is changed in save above)
								if(!$task->attributes()->LASTMODSTRING) $task->addAttribute('LASTMODSTRING', date('Y-m-d h:i:s A', strtotime($event->updated->text)));
								else $task->attributes()->LASTMODSTRING= date('Y-m-d h:i:s A', strtotime($eventNew->updated->text)) ;
								echo ".";
							}
				//		} catch (Zend_Gdata_App_Exception $e) {
					//		echo "zend error saving\n";
							// var_dump($e);
					//	}

							
							
							

					}
				}
					
				// *** MODIFY LOCAL TO DO LIST
				if($aGCalEvents[$taskEXTId]["eModDT"]>$taskModDT) //google is newer, modify local todolist data
				{
					if($anyTasksProcessed<2) $anyTasksProcessed=2; //we created or modified tasks
	    	
					if(!$task->attributes()->TITLE) $task->addAttribute('TITLE', $aGCalEvents[$taskEXTId]["eTitle"]);
					else $task->attributes()->TITLE=$aGCalEvents[$taskEXTId]["eTitle"];

					if(!$task->attributes()->DUEDATESTRING) $task->addAttribute('DUEDATESTRING', date('Y-m-d h:i:s A', $aGCalEvents[$taskEXTId]["stDate"]));
					else $task->attributes()->DUEDATESTRING=date('Y-m-d h:i:s A', $aGCalEvents[$taskEXTId]["stDate"]);

					// modify local ToDoList modified date/time so we dont sync back to google next time
					if(!$task->attributes()->LASTMODSTRING) $task->addAttribute('LASTMODSTRING', date('Y-m-d h:i:s A', $aGCalEvents[$taskEXTId]["eModDT"]));
					else $task->attributes()->LASTMODSTRING= date('Y-m-d h:i:s A', $aGCalEvents[$taskEXTId]["eModDT"]) ;
						
					echo "local modified ".$aGCalEvents[$taskEXTId]["eModDT"]." ".$taskModDT."\n";
				}
					

			}
		}


		if($createTask==1) //create new task
		{
			echo "create google event ".$tTile."\n";
			if($anyTasksProcessed<2) $anyTasksProcessed=2; //we created or modified tasks
			$eventStart = date('Y-m-d\TH:i:sP', $dueDateDT);
			$eventEnd = $eventStart; //base this off time estimate field??
			$newEvent = $gdataCal->newEventEntry();
			$newEvent->title = $gdataCal->newTitle($tTile);
			$when = $gdataCal->newWhen();
			$when->startTime = $eventStart;
			$when->endTime = $eventEnd;
			$newEvent->when = array($when);
			//$newEvent->where  = array($gdataCal->newWhere($where)); //todolist does not support location?
			$newEvent->content = $gdataCal->newContent($gdataCal->newContent($tComments));
			$newEvent->content->type = 'text'; // html??
				
			$extProp = $gdataCal->newExtendedProperty("TDL_GoodLastMod", time());
			$extProps = array_merge($newEvent->extendedProperty, array($extProp));
			$extProp = $gdataCal->newExtendedProperty("TDL_TaskID", $tTaskID);
			$extProps = array_merge($extProps, array($extProp));
			$newEvent->extendedProperty=$extProps;

			//TODO: add reccurence string  $eventOld->recurrence
				
				
			//And at last, the insert is set to the calendar URL found above
			$createdEvent = $gdataCal->insertEvent($newEvent, $taskCalURL);
				
			if(!$task->attributes()->EXTERNALID) $task->addAttribute('EXTERNALID', $createdEvent->id->text);
			else $task->attributes()->EXTERNALID=$createdEvent->id->text;

			// modify local ToDoList modified date/time so we dont sync back to google next time (google modified date is changed in save above)
			if(!$task->attributes()->LASTMODSTRING) $task->addAttribute('LASTMODSTRING', date('Y-m-d h:i:s A', strtotime($createdEvent->when[0]->startTime)));
			else $task->attributes()->LASTMODSTRING= date('Y-m-d h:i:s A', strtotime($createdEvent->when[0]->startTime));
		}
	}
	 
	if(count($task)>0) //this task have any children, if so process them
	{
		$tempVar=loopTASKS($task);
		if($anyTasksProcessed<1) $anyTasksProcessed=$tempVar;
	}
}

return $anyTasksProcessed;
}


//*****************

$client = getClientLoginHttpClient($googUName, $googPas);
$gdataCal = new Zend_Gdata_Calendar($client);
$calFeed = $gdataCal->getCalendarListFeed(); //list of calendars


//build array of task/calendar info  TODO: possible future optimization, refactor this to use the calfeed directory if easy way to access by id
foreach ($calFeed as $calendar) {
	$eventFeed = $gdataCal->getCalendarEventFeed($calendar->content->src);
	foreach ($eventFeed as $event) {
		$aGCalEvents[$event->id->text]["eTitle"]=$event->title->text; //summary/title
		$aGCalEvents[$event->id->text]["stDate"]=$event->when[0]->startTime; //start date / time
		$aGCalEvents[$event->id->text]["endDate"]=$event->when[0]->endTime; //end date / time
		$aGCalEvents[$event->id->text]["calID"]=$calendar->content->src; //calendar id / url
		$aGCalEvents[$event->id->text]["eModDT"]=strtotime($event->updated->text); //modified date
		$aGCalEvents[$event->id->text]["calTitle"]=$calendar -> title -> text; //calendar title (category)
	  
		if($event->extendedProperty) //grab extended properties
		{
			foreach ($event->extendedProperty as $exProp) {
				$aGCalEvents[$event->id->text][$exProp->name]=$exProp->value;
			}
		}
	  
		if($event->recurrence) //does it have a recurrence string?
		{
			$aGCalEvents[$event->id->text]["recurrence"]=$event->recurrence->text;
			 
		}
	  
		if($event->content) //is there content?  TODO: check the type ?
		{
			$aGCalEvents[$event->id->text]["content"]=$event->content->text;
		}


	  
	} //for events
} // for calendars




if (file_exists($toDoPath)) { //look for ToDoList file

	$xml = simplexml_load_file($toDoPath); //load the xml to do list
	echo "processing to do list file ".$toDoPath."\n";
	$anyTasksProcessed=loopTASKS($xml); //scan the ToDoList file and sync with google

	if($anyTasksProcessed==0)echo "Nothing to process from ToDoList, already in sync or no future due dates. \n";
	 
	 
	// now scan google tasks and look for anything not synced with ToDoList
	foreach ($aGCalEvents as $gCalEvent) {
		if(!$gCalEvent['TDL_TaskID']){ //do we have a task id?  if not, we need to create this task in ToDoList locally!
			echo "new task from google!\n";
			$newXMLTask = $xml->addChild('TASK'); //add local xml task
			$newXMLTask->addAttribute('TITLE', $gCalEvent["eTitle"]);
			$newXMLTask->addAttribute('COMMENTS', $gCalEvent["content"]);
			//$newXMLTask->addAttribute('STARTDATESTRING', date('Y-m-d h:i:s A', strtotime($gCalEvent["stDate"])));
			$newXMLTask->addAttribute('CATEGORY',$gCalEvent["calTitle"]);
			$newXMLTask->addAttribute('DUEDATESTRING', date('Y-m-d h:i:s A', strtotime($gCalEvent["stDate"])));
			$newXMLTask->addAttribute('EXTERNALID', $gCalEvent["calID"]);

			if($gCalEvent["recurrence"]) //is this a recurring event?
			{
				//TODO: write a reccurence element out to local xml data
				// one example: <RECURRENCE REGULARITY="2" REGPECIFIC1="1" REGPECIFIC2="68" RECURREUSE="0" REGFROMDUE="1">Weekly</RECURRENCE>
				//echo $gCalEvent["recurrence"];
				//http://code.google.com/apis/calendar/data/1.0/developers_guide_php.html#CreatingRecurring
			//http://code.google.com/apis/gdata/docs/1.0/elements.html#gdRecurrence
			
			}
			else
			{
				$startDT=strtotime($gCalEvent["stDate"]);
				$endDT=strtotime($gCalEvent["endDate"]);
				$diffDT=($endDT-$startDT)/60; //difference in mins TODO: improve this to set the time as a decimal fraction of H/D/W/M/Y etc.
				$newXMLTask->addAttribute('TIMEESTIMATE', $diffDT);
				$newXMLTask->addAttribute('TIMEESTUNITS', "m"); //for now only seconds...
					
			}


			//we do not update google at all at this point, google will update in sync back  TODO: consider syncing google to xml first, then back
			 


			 
		} //if it is not in local todolist (tdl_taskid)
	} //for gcalevents

	//write out changes to local xml ToDoList
	$dom = new DOMDocument('1.0');
	//$dom->preserveWhiteSpace = true;
	//$dom->formatOutput = true;
	$dom->loadXML($xml->asXML());
	//echo $dom->saveXML();
	$dom->save($toDoPath);
	echo "local changes written";

} else {
	exit('Failed to open '.$toDoPath);
}














?>

